/*
 * @Author: your name
 * @Date: 2021-04-09 16:57:11
 * @LastEditTime: 2021-04-10 12:03:49
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \TypeScript\18-类型保护.ts
 */
// 类型保护与区分类型
// 联合类型时候于哪些可以为不同类型的情况

// 但是如果我们就想要来判断一下，
// 最后pet对象是否拥有swim和fly属性的时候我们又该咋办？

// 传统的方式就是if检查成员是否存在，但是TypeScript的类型检查会直接报错，根本不给检查的机会
// 此时，我么和就可以使用类型断言的方式来强行指定类型来绕过TypeScript的排查机制

interface Bird {
    fly();
    layEggs();
}
interface Fish {
    swim();
    layEggs();
}
function getSmallPet(): Fish | Bird {
    return {
        layEggs() { },
        swim() { },
        fly() { }
    };
}
let pet = getSmallPet();
// 每一个成员访问都会报错
// if (pet.swim) {
//     pet.swim();
// } else if (pet.fly) {
//     pet.fly
// }
// 使用类型断言
let pet2 = getSmallPet();
if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
} else {
    (<Bird>pet).fly();
}
// 用户自定义的类型保护
// 上面的代码中，为了确保能够正常判断，我们用了很多次类型断言，这种方法泰国麻烦了
// 假若我们一旦检查过类型，就嫩在之后的每个分支里清楚的知道pet的类型就好了
// 在TypeScript里的类型保护机制让他成为了现实。类型保护就是一些表达式，
// 他们会在运行时检查以确保在某个作用域里的类型
// 要定义一个类型保护， 我们只要简单的定义一个函数，他的返回值时一个 类型谓词：
// 在下个例子里， pet is Fish就是类型谓词。谓词为parameterName is Type 这种形式，
// parameterName必须是来自于当前函数签名里的一个参数名
let pet3 = getSmallPet()
function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}
// 每当使用一些变量调用 isFish时，
// TypeScript会将变量缩减为那个具体的类型，
// 只要这个类型与变量的原始i类型是兼容的
// ‘swim’ 和 ‘fly’ 调用都没有问题
if (isFish(pet)) {
    pet.swim();
} else {
    pet.fly();
}
// type类型保护
// 幸运的是，现在我们不必将typeof x === 'numbe'抽象成一个函数，
// 因为TypeScript可以将它识别为一个类型保护。
// 也就是说我们可以直接在代码里抽查类型了
function isNumber(x: any): x is number {
    return typeof x === 'number';
}
function isString(x: any): x is string {
    return typeof x === 'string';
}
function padLeft2(value: string, padding: string | number) {
    if (isNumber(padding)) {
        return Array(padding + 1).join(" ") + value;
    }
    if (isString(padding)) {
        return padding + value;
    }
    throw new Error(`Expection string or number, got '${padding}'.`);
}
// => 优化
function padLeft3(value: string, padding: number | string) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Excepted string or number, got '${padding}'`);
}
// 这些 typeof类型保护只有两种形式能被识别： typeof v === 'typename' 和 typeof v !== 'typename',
// "typename" 必须是“number” ,"string","boolean" 或 “symbol”
// 但hiTypeScript并不会组织你与其他字符串比较，语言不会把那些表达式识别为类型保护

// interfaceof 类型保护
// interfaceof 类型保护是用过构造函数来细化类型的一种方式
// interface 的右侧要求是一个构造函数，TypeScript将细化为：
// A.此构造函数的prototype属性的类型，如果它的类型不为any的话
// B.构造签名所返回的类型的联合
// 以此顺序
interface Padder {
    getPaddingString(): string;
}
class SpaceRepeatingPadder implements Padder {
    constructor(private numSpaces: number) { }
    getPaddingString() {
        return Array(this.numSpaces + 1).join(" ");
    }
}
class StringPadding implements Padder {
    constructor(private value: string) { }
    getPaddingString() {
        return this.value;
    }
}
function getRandomPadder() {
    return Math.random() < 0.5 ?
        new SpaceRepeatingPadder(4) :
        new StringPadding(" ");
}
// 类型为SpaceRepeatingPadder | StringPadder
let padder:Padder = getRandomPadder();
if(padder instanceof SpaceRepeatingPadder){
    // 类型细化为SpaceRepeatingPadder
    padder;
}
if(padder instanceof StringPadding){
    // 类型细化为StringPadder
    padder;
}

// 可以为null的类型之可选参数
// 似乎用了 --strictNullChecks,可选参数会被Zion给地加上 | undefined
function f(x:number,y?:number){
    return x+(y||0);
}
f(1,2);
f(1);
f(1,undefined);
f(1,null)

// 可选属性也会有同样的处理
class C{
    a:number;
    b?:number;
}
let c = new C();
c.a = 12;
c.a = undefined;
c.b = 13;
c.b = undefined;
c.b = null;


function test(x:number,y?:string|undefined):void{

}
test(1,null)

// 可以为null的类型之类型保护和类型断言
// 由于可以为null的类型是通过联合类型实现
// 那么你需要使用理想保护来去除nll

// 很明显的去除了null
function f2(sn:string|null):string{
    if(sn == null){
        return "default";
    }else{
        return sn;
    }
}

// 也可以使用短路运算符
function f3(sn:string|null):string{
    return sn || "default";
}



// 可以为null的类型值类型保护和类型断言
// 如果编译器不能够去除null或undefined， 
// 你可以使用类型断言手动去除
// 语法是添加!后缀: identifier!从identifier的类型里去除了null和undefined:
function fixed(name:string|null):string{
    function postfix(epithet:string){
        return name!.charAt(0) + '. the' + epithet;
    } 
    name = name || "Bob"
    return postfix('great');
}
// 本历史与了嵌套函数，因为编译器无法去除嵌套汉化的null(除非是立即调用的函数表达式)。
// 因为他无法跟踪所有对其那套函数的调用，尤其是你将内层函数作为外城函数的返回值
// 如果无法知道函数在哪里被调用，就无法知道嗲用时name的类型


// 类型别名
// 类型别名会给一个类型起个新名字，类型别名有时和接口很小，但是可以作用于原始值，
// 联合类型，元组以及其他任何你需要手写的类型
// 起别名不会新建一个类型 - 它出啊关键了一个新名字里来引用那个类型，
// 给原始类型起别名通常没什么用，尽管可以作为文档的一种形式使用
// type 新类型名称 = 原始类型名称
type Name = string;
type NameResolver = ()=>string;
type NameOrResolver = Name | NameResolver;
function getName(n:NameOrResolver):Name{
    if(typeof n === 'string'){
        return n;
    }else{
        return n();
    }
}
// 同接口一样，类型别名也可以是泛型 - 我们可以添加类型参数并且在别名声明的右侧传入：
type Contaainer<T> = {value:T};
// 也可以使用类型别名来在属性里引用自己
type Tree<T> = {
    value:T;
    left:Tree<T>;
    right:Tree<T>;
}
// 然而，类型别名不嫩和出现在声明右侧的任何地方
type Yikes = Array<Yikes>;

type LinkedList<T> = T & {next:LinkedList<T>};
interface Person {
    name:string;
}
// 与交叉类型一起使用，我们可以常见出一些十分稀奇古怪的类型
var people:LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;
var s = people.next.next.next.next.name;
var s = people.next.next.next.next.next.name;

